using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;

namespace UserInterface
{
    public enum Parent { Type1, Type2 };
    public enum Direction { E, W, N, S, EN, NW, WS, SE, ES, SW, WN, NE }
    public enum Flux { ingress, egress, InOut };

    [Serializable()]
    public class Lane
    {
        //fields
        ///LaneID: is the Lane identifier, every lane should an ID.
        /// NumberOfCars: specify the number of cars in lane. there is fix number of maximum cars per lane.
        /// CarList: is a container, which is the List of cars.
        /// movementDirection: specify which direction of the lane the car can move.
        ///MAXCARS: is the maximum number of cars which is set to 20
        ///Enum Parent: every lane reside in crossing and there types of crossing, Parent is the type of crossing the lane reside.
        ///Enum Direction: everylane is on different directions based on East West, North and South.  
        ///this demonstrate in which direction a car can follow based on the directions of the lane
        ///Enum Flux: not everylane receive imcoming cars on the crossing, there are some lane which drive in are forbiden, 
        ///Ingress specify that this type of lane can receive cars while egress specify that cars can only drive out of this lane. 
        ///InOut specify oposite lane receive and discharging cars.
        ///reacheDestiantion: when can drive into lane it can alway reach a destination and egress have departure, 
        ///a boolean to check it car reaches its destiantion then another car can be add.
        ///Hashtable: possiblitie on how car can move on the lane, it can be clockwise and anticlockwise. 
        /// </summary>
        public string LaneID;
        public int NumberOfCars = 0;
        public int NumberOfPedos = 0;
        private List<Car> CarList;
        private static Hashtable laneDirectionPossibilitiesClockWise;
        private static Hashtable laneDirectionPossibilitiesAntiClockWise;
        
        //[NonSerialized]
        //public Timer tmr_move = new Timer();
        private List<Lane> possibleDestinations;
        private List<Lane> myNeighbours;
        public bool canMove = false;
        public int speed = -1;
        Random rdm = new Random();
     
        
        //properties
        /// <summary>
        /// Laneid: setting the name of the Lane, for example N1
        /// Parent: setting the type of crossing a lane belongs
        /// Start: the possition at which a car enters the lane
        /// end: the possition at which the car leaves the lane, usually the destination
        /// Direction: the direction at which the car move
        /// </summary>
        
        public string LaneId { get; set; }
        public Parent parent { get; set; }
        public Point start { get; set; }
        public Point end { get; set; }
        public List<PedLane> pl { get; set; }
        public Image img { get; set; }
        public int MAXCARS { get; set; }
        public Direction direction { get; set; }
        public Flux flux { get; set; }

        //constructors
        /// <summary>
        /// Lane: an empty constructor defination
        /// Lane: constructor of the lane with neccessary parameter
        /// </summary>
        public Lane() { }

        public Lane(string id, Point str, Point end, Direction dr, Parent prnt, Image pic)
        {
            this.LaneId = id;
            this.start = str;
            this.end = end;
            this.direction = dr;
            this.parent = prnt;
            this.CarList = new List<Car>();
            this.possibleDestinations = new List<Lane>();
            this.myNeighbours = new List<Lane>();
            //tmr_move.Interval = 100;
            //tmr_move.Tick += new System.EventHandler(Move);
            this.img = pic;
            canMove = false;
            MAXCARS = 5;
        }

        ~Lane()
        {
            this.CarList = null;
        }

        public virtual void ResetLane()
        {
            this.CarList = null;
            this.CarList = new List<Car>();
            this.NumberOfCars = 0;
        }

        public Lane(string ID, Direction dir)
        {
            this.LaneId = ID.ToString();
            this.CarList = new List<Car>();
            this.direction = dir; /// lane direction manually set when creation from the enum possibilities
            this.flux = Flux.InOut;	/// need to determine this.
            this.NumberOfCars = CarList.Count;
            canMove = false;
            //this.hasNeighbours = false;
            laneDirectionPossibilitiesClockWise = new Hashtable();   ///
            laneDirectionPossibilitiesClockWise.Add(Direction.E.ToString(), Direction.EN);
            laneDirectionPossibilitiesClockWise.Add(Direction.EN.ToString(), Direction.N);
            laneDirectionPossibilitiesClockWise.Add(Direction.N.ToString(), Direction.NW);
            laneDirectionPossibilitiesClockWise.Add(Direction.NW.ToString(), Direction.W);
            laneDirectionPossibilitiesClockWise.Add(Direction.W.ToString(), Direction.WS);
            laneDirectionPossibilitiesClockWise.Add(Direction.WS.ToString(), Direction.S);
            laneDirectionPossibilitiesClockWise.Add(Direction.S.ToString(), Direction.SE);
            laneDirectionPossibilitiesClockWise.Add(Direction.SE.ToString(), Direction.E);

            laneDirectionPossibilitiesAntiClockWise = new Hashtable();  ///
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.E.ToString(), Direction.ES);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.ES.ToString(), Direction.S);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.S.ToString(), Direction.SW);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.SW.ToString(), Direction.W);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.W.ToString(), Direction.WN);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.WN.ToString(), Direction.N);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.N.ToString(), Direction.NE);
            laneDirectionPossibilitiesAntiClockWise.Add(Direction.NE.ToString(), Direction.E);
        }

        /// <summary>
        /// <b>It started as a test, it will change to a single object instead</b>
        /// Checks if a neighbour exists 
        /// </summary>
        /// <returns><b>true</b> if exists, <b>false</b> if not</returns>
        public bool TrafficFlow()
        {
            if (this.flux == Flux.InOut)
                return true;
            return false;

        }

        public int getNofPedos() { return NumberOfPedos - 1; }

        public bool hasPedestrians()
        {
            for (int i = 0; i < pl.Count; i++)
            {
                if (pl[i].pedNum > 0)
                { return true; }
            }
            return false;
        }

        /*
          public void DecideDestination()
          { 
          // kind of little bit stuck with the implementation here. check if a car is one LaneID then the reachedDestination is point X
          // any idea would be appreciated
        
          /*
            The Lane should have a reference of the Lanes (of other crossings as well) arround.
            // If the crossing is type 1 or type 2 should be resolved when the crossing is addded to the Map.
            // And the references should be calculated at that time.
          
            The Decide destination is called when a car is added in the current lane.
            If the current lane is an exit lane. The function should check the possible destinations in the new crossing that the car is heading.
            If the Lane is an exit lane with no crossings to connect the car should get a null destination lane.
			Not sure if this is right.
			
          }
         */


        public void AddCarLane(Car c) // add car to lane,  checking if there is no car on the lane or if there is no maximum car
        {
            c.departure = this;            
            CarList.Add(getGapOfLane(c));
            if (speed != -1)
                CarList[CarList.Count-1].speed = speed; 
            NumberOfCars++;
        }
        public void RemoveCarLaneStart() // remove car from lane front
        {
            CarList.RemoveAt(0);
            NumberOfCars--;
        }


        public List<Car> GetCarList() // Get Car Lane
        {
            if ((CarList != null) && (CarList.Count > 0))
            {
                return this.CarList;
            }
            else
            {
                return null;
            }
        }

        public Car GetCarfromList(int position) // get car from lane
        {
            if ((CarList != null) && (CarList.Count > 0) && (position < CarList.Count))
            {
                return this.CarList[position];
            }
            else
                return null;
        }
        // Experimental.. Check Moving Object draw boolean | I don't know yet if this is the best method.
        public Point[] getCarsToDraw()
        {
            Point[] points = new Point[CarList.Count]; 
                for (int i = 0; i < CarList.Count; i++ )
                points[i] = CarList[i].position; 
                return points;

        }


        public virtual void Move(object s, EventArgs e)
        {    
            for (int i = 0; i < CarList.Count; i++)
            {
                if (i == 0)
                {                                               // timer.Enabled
                    CarList[0].move(this.LaneId, this.end, parent, canMove);
                }
                else 
                {                                                           // always false
                    CarList[i].move(this.LaneId,getGapOfCar(CarList[i-1]), parent, false); 
                } 
            }
        }

        //this is needed in the main metthod because if a lane takes only incoming car, that means car cant drive in and vice versa...
        // if (Lane.Direction != 'Incoming') create object of lane and call methods  
        // Lane objLane = new Lane ("A");
        // if (objLane.Direction != 'Incoming') objLane.     

        /// with this code need to be modify and check in which direction the car has to move since we have N, E, W, S


        public Direction decideDirection()
        {
            Flux strflux;           ///get flux of lane
            Direction strDir;       ///get direction of lane

            strDir = this.direction;	/// the function should be called for every Lane. When car leaves a lane and enter other lane its direction will be that of the lane.
            strflux = this.flux;		/// Please remeber direction is not of car its that of lane depending upon the flow of the lane.

            if (strflux.ToString() == "ingress")		/// tipically Flux InOut has no significance. The direction of car is determined through flux ingress or engress. 
            {
                return (Direction)laneDirectionPossibilitiesClockWise[strDir];
            }
            else if (strflux.ToString() == "engress")
            {
                return (Direction)laneDirectionPossibilitiesAntiClockWise[strDir];
            }
            else
            {
                return 0;
            }
        }

        bool hasNeighbours = false;
        public void addNeighbour(Lane neighbour)
        {
            if (neighbour == null)
            { 
                hasNeighbours = true;
                return;
            }
            this.myNeighbours.Add(neighbour);
        }

        //This is the solution I have been thinking about :)
        public Lane PickDestination()
        {
            if (this.flux == Flux.ingress || this.flux == Flux.InOut)
            {
                if (this.possibleDestinations.Count == 1)
                {
                    return possibleDestinations[0];
                }
                else
                {
                    int index = rdm.Next(possibleDestinations.Count);
                    try
                    {
                        return possibleDestinations[index];
                    }
                    catch(ArgumentOutOfRangeException)
                    {
                        return null;
                    }
                }
            }
            else
            {
                if (this.myNeighbours.Count == 1)
                {
                    return myNeighbours[0];
                }
                else
                {
                    Random rdm = new Random();
                    int index = rdm.Next(myNeighbours.Count);
                    try
                    {
                        return myNeighbours[index];
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        return null;
                    }
                }
            }
        }

        /// <summary>
        /// Checks if the lane is full of cars
        /// </summary>
        /// <returns><b>true</b> if is full, <b>false</b> if not</returns>
        public bool isFull()
        {
            return this.CarList.Count >= MAXCARS;
        }

        public bool HasNeighbours()
        {
            return this.myNeighbours.Count != 0 || hasNeighbours;
        }

        public void addDestination(Lane dest)
        {
            this.possibleDestinations.Add(dest);
        }

        private Car getGapOfLane(Car car)
        {
            switch (this.LaneId)
            {
                case "N1":
                case "N2":
                case "S1":
                    car.pos.Y -= 20;
                    return car;
                case "E1":
                case "E2":
                case "W1":
                    car.pos.X += 20;
                    return car;
                case "S2":
                case "S3":
                case "N3":
                    car.pos.Y += 20;
                    return car;
                case "W2":
                case "W3":
                case "E3":
                    car.pos.X -= 20;
                    return car;
                default:
                    return car;

            }

        }

        private Point getGapOfCar(Car car)
        {
            Point newPoint = car.pos;

            switch (this.LaneId)
            {
                case "N1":
                case "N2":
                case "S1":
                  newPoint.Y -= 25;
                    return newPoint;
                case "E1":
                case "E2":
                case "W1":
                    newPoint.X += 25;
                    return newPoint;
                case "S2":
                case "S3":
                case "N3":
                    newPoint.Y += 25;
                    return newPoint;
                case "W2":
                case "W3":
                case "E3":
                    newPoint.X -= 25;
                    return newPoint;
                default:
                    return newPoint;

            }

        }

    }
}
